package com.atguigu.service.impl;

import com.atguigu.cache.ShopCache;
import com.atguigu.constant.RedisConst;
import com.atguigu.entity.ProductSalePropertyKey;
import com.atguigu.entity.SkuImage;
import com.atguigu.entity.SkuInfo;
import com.atguigu.exception.SleepUtils;
import com.atguigu.mapper.ProductSalePropertyKeyMapper;
import com.atguigu.mapper.SkuSalePropertyValueMapper;
import com.atguigu.result.RetVal;
import com.atguigu.service.ProductSalePropertyKeyService;
import com.atguigu.service.SkuDetailService;
import com.atguigu.service.SkuImageService;
import com.atguigu.service.SkuInfoService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
public class SkuDetailServiceImpl implements SkuDetailService {

    @Autowired
    private SkuInfoService skuInfoService;
    @Autowired
    private SkuImageService skuImageService;
    @Autowired
    private ProductSalePropertyKeyMapper salePropertyKeyMapper;
    @Autowired
    private SkuSalePropertyValueMapper skuSalePropertyValueMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RBloomFilter<Long> skuBloomFilter;

    //    @Cacheable(value = "xxx",key = "xxx")
    @ShopCache(prefix = "skuInfo")
//    @ShopCache(prefix = "#{#params}")
    @Override
    public SkuInfo getSkuInfo(Long skuId) {
        SkuInfo skuInfo = getSkuInfoFromDb(skuId);
//        SkuInfo skuInfoFromRedis = getSkuInfoFromRedis(skuId);
//        SkuInfo skuInfo = getSkuInfoFromRedisWithThreadLocal(skuId);
//        SkuInfo skuInfo = getSkuInfoFromRedisson2(skuId);
        return skuInfo;
    }

    //1.1 从缓存中查询商品基本信息
    public SkuInfo getSkuInfoFromRedis1(Long skuId) {
        //a.从缓存中去拿数据  sku:24:info
        String cacheKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
        SkuInfo cacheSkuInfo = (SkuInfo) redisTemplate.opsForValue().get(cacheKey);
        if (cacheSkuInfo == null) {
            //b.从数据库中拿skuInfo的数据信息
            SkuInfo skuInfoFromDb = getSkuInfoFromDb(skuId);
            //不为空也需要放缓存
            redisTemplate.opsForValue().set(cacheKey, skuInfoFromDb, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
            return skuInfoFromDb;
        }
        //d.缓存中如果有值,直接返回
        return cacheSkuInfo;
    }

    //1.2 从缓存中查询商品基本信息--双重检索--防御缓存穿透攻击
    public SkuInfo getSkuInfoFromRedis4(Long skuId) {
        //a.从缓存中去拿数据  sku:24:info
        String cacheKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
        SkuInfo cacheSkuInfo = (SkuInfo) redisTemplate.opsForValue().get(cacheKey);
        if (cacheSkuInfo == null) {
            //b.从数据库中拿skuInfo的数据信息
            SkuInfo skuInfoFromDb = getSkuInfoFromDb(skuId);
            //判断skuInfoFromDb是否为空 如果为空
            if (skuInfoFromDb == null) {
                //告诉你数据库确实没有该数据 以后别查数据库了
                SkuInfo emptySkuInfo = new SkuInfo();
                //c.放到redis当中
                redisTemplate.opsForValue().set(cacheKey, emptySkuInfo, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                return emptySkuInfo;
            }
            //不为空也需要放缓存
            redisTemplate.opsForValue().set(cacheKey, skuInfoFromDb, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
            return skuInfoFromDb;
        }
        //d.缓存中如果有值,直接返回
        return cacheSkuInfo;
    }

    //1.3 从缓存中查询商品基本信息--融入布隆过滤器--能抵御随机缓存穿透
    public SkuInfo getSkuInfoFromRedis3(Long skuId) {
        //a.从缓存中去拿数据  sku:24:info
        String cacheKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
        SkuInfo cacheSkuInfo = (SkuInfo) redisTemplate.opsForValue().get(cacheKey);
        if (cacheSkuInfo == null) {
            //先问一下,布隆过滤器是否有该id,如果有才查数据库
            boolean flag = skuBloomFilter.contains(skuId);
            SkuInfo skuInfoFromDb = null;
            if (flag) {
                //b.从数据库中拿skuInfo的数据信息
                skuInfoFromDb = getSkuInfoFromDb(skuId);
            }
            //不为空也需要放缓存
            redisTemplate.opsForValue().set(cacheKey, skuInfoFromDb, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
            return skuInfoFromDb;
        }
        //d.缓存中如果有值,直接返回
        return cacheSkuInfo;
    }

    //3.1 使用Redisson查询商品基本信息
    private SkuInfo getSkuInfoFromRedisson1(Long skuId) {
        //a.从缓存中去拿数据  sku:24:info
        String cacheKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
        SkuInfo cacheSkuInfo = (SkuInfo) redisTemplate.opsForValue().get(cacheKey);
        if (cacheSkuInfo == null) {
            String lockKey = "lock-" + skuId;
            RLock lock = redissonClient.getLock(lockKey);
            lock.lock();
            try {
                //缓存双检查
                cacheSkuInfo = (SkuInfo) redisTemplate.opsForValue().get(cacheKey);
                if (cacheSkuInfo == null) {
                    //b.从数据库中拿skuInfo的数据信息
                    SkuInfo skuInfoFromDb = getSkuInfoFromDb(skuId);
                    cacheSkuInfo = skuInfoFromDb;
                    //c.放到redis当中
                    redisTemplate.opsForValue().set(cacheKey, skuInfoFromDb, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                }
                return cacheSkuInfo;
            } finally {
                lock.unlock();
            }
        }
        //d.缓存中如果有值,直接返回
        return cacheSkuInfo;
    }

    //3.2 使用Redisson查询商品基本信息--布隆过滤器---防止随机缓存穿透
    private SkuInfo getSkuInfoFromRedisson2(Long skuId) {

        Map map = new HashMap<>();
        map.put(1,1);

        //a.从缓存中去拿数据  sku:24:info
        String cacheKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
        SkuInfo cacheSkuInfo = (SkuInfo) redisTemplate.opsForValue().get(cacheKey);
        if (cacheSkuInfo == null) {
            String lockKey = "lock-" + skuId;
            RLock lock = redissonClient.getLock(lockKey);
            lock.lock();
            try {
                //缓存双检查
                cacheSkuInfo = (SkuInfo) redisTemplate.opsForValue().get(cacheKey);
                if (cacheSkuInfo == null) {
                    //先问一下,布隆过滤器是否有该id,如果有才查数据库
                    boolean flag = skuBloomFilter.contains(skuId);
                    SkuInfo skuInfoFromDb = null;
                    if (flag) {
                        //b.从数据库中拿skuInfo的数据信息
                        skuInfoFromDb = getSkuInfoFromDb(skuId);
                    }
                    //不为空也需要放缓存
                    redisTemplate.opsForValue().set(cacheKey, skuInfoFromDb, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                    return skuInfoFromDb;
                }
                return cacheSkuInfo;
            } finally {
                lock.unlock();
            }
        }
        //d.缓存中如果有值,直接返回
        return cacheSkuInfo;
    }

    //2 利用redis+lua+ThreadLocal实现商品基本信息查询
    public ThreadLocal<String> threadLocal = new ThreadLocal();

    public SkuInfo getSkuInfoFromRedisWithThreadLocal(Long skuId) {
        //a.从缓存中去拿数据  sku:24:info
        String cacheKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
        SkuInfo cacheSkuInfo = (SkuInfo) redisTemplate.opsForValue().get(cacheKey);
        if (cacheSkuInfo == null) {
            //假如这上面还要做很多操作 取缓存 查逻辑等等 100行代码
            String token = threadLocal.get();
            boolean acquireLock = false;
            String lockKey = "lock-" + skuId;
            //假如该线程在自旋的时候没有拿到锁
            //线程刚开始进来的时候没有拿到锁
            if (token == null) {
                //假如这上面还要做很多操作 取缓存 查逻辑等等 100行代码
                token = UUID.randomUUID().toString();
                //利用reids的setnx命令
                acquireLock = redisTemplate.opsForValue().setIfAbsent(lockKey, token, 3, TimeUnit.SECONDS);
            } else {
                acquireLock = true;
            }
            if (acquireLock) {
                //b.从数据库中拿skuInfo的数据信息
                SkuInfo skuInfoFromDb = getSkuInfoFromDb(skuId);
                //c.放到redis当中
                redisTemplate.opsForValue().set(cacheKey, skuInfoFromDb, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);

                String redisToken = (String) redisTemplate.opsForValue().get(lockKey);
                String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                redisScript.setScriptText(luaScript);
                redisScript.setResultType(Long.class);
                redisTemplate.execute(redisScript, Arrays.asList(lockKey), token);
                //防止内存泄漏
                threadLocal.remove();
                return skuInfoFromDb;
            } else {
                //自旋 只去做一件事情 拿锁
                for (; ; ) {
                    SleepUtils.sleepMillis(50);
                    boolean retryAcquireLock = redisTemplate.opsForValue().setIfAbsent(lockKey, token, 3, TimeUnit.SECONDS);
                    if (retryAcquireLock) {
                        //当前线程自旋成功 把拿到锁的token 放到map中
                        threadLocal.set(token);
                        break;
                    }
                }
                return getSkuInfoFromRedisWithThreadLocal(skuId);
            }
        } else {
            //防止内存泄漏
            threadLocal.remove();
            //d.缓存中如果有值直接返回
            return cacheSkuInfo;
        }
    }

    // 直接查询数据库
    private SkuInfo getSkuInfoFromDb(Long skuId) {
        //查询单个skuInfo信息
        SkuInfo skuInfo = skuInfoService.getById(skuId);
        if (skuInfo != null) {
            QueryWrapper<SkuImage> wrapper = new QueryWrapper<>();
            wrapper.eq("sku_id", skuId);
            List<SkuImage> skuImageList = skuImageService.list(wrapper);
            skuInfo.setSkuImageList(skuImageList);
        }
        return skuInfo;
    }

    @ShopCache(prefix = "getSpuSalePropertyAndSelected", enableBloom = false)
    @Override
    public List<ProductSalePropertyKey> getSpuSalePropertyAndSelected(Long productId, Long skuId) {
        Map map = new HashMap<>();
        map.put(1,1);
        return salePropertyKeyMapper.getSpuSalePropertyAndSelected(productId, skuId);
    }

    @ShopCache(prefix = "getSalePropertyAndSkuIdMapping", enableBloom = false)
    @Override
    public Map<Object, Object> getSalePropertyAndSkuIdMapping(Long productId) {
        Map<Object, Object> salePropertyAndSkuId = new HashMap<>();
        List<Map> retMap = skuSalePropertyValueMapper.getSalePropertyAndSkuIdMapping(productId);
        for (Map map : retMap) {
            //key是销售属性的id对, value是skuId
            salePropertyAndSkuId.put(map.get("sale_property_value_id"), map.get("sku_id"));
        }
        return salePropertyAndSkuId;
    }
}
